from BGP import Update
import Graph, Path, StatTracker, State
from Lib import Util
import string, os, datetime, time, exceptions

TOTAL_PEERS = 40
STAT_TYPES = ["paths", "act_nodes", "tot_nodes", "edges"]

class PrefixGraph:

    def __init__ (self, startDate, endDate, stateDBHelper):
        self.graph = Graph.Graph()
        self.paths = {}
    	self.stats = {}
    	self.dbHelper = stateDBHelper
    
    	self.numAnnounces = 0
    	self.numDupAnnounces = 0
    	self.numWithdrawals = 0
    	self.activePaths = 0
        
        self.startDate = startDate
        self.endDate = endDate
        self.dataStartDate = startDate
    	self.converged = False #should normally be set to false to start
    	self.convergenceTime = 0

        for stat in STAT_TYPES:
            self.stats[stat] = StatTracker.StatTracker()
            
        self.loadExistingState()

    # Given an update and a match color, make the appropriate
    # changes to the graph
    def updateGraph(self, utcTime, type, peerAs, asPath):

        if type == 'A': # Possibly a new path to add      
            self.numAnnounces += 1
            
            if peerAs not in self.paths:
                self.paths[peerAs] = Path.Path(asPath, peerAs) #path should NOT be active at this point
                
            if self.paths[peerAs].isActive():
                if not self.paths[peerAs].containsPath(asPath): #if our new path doesn't match the existing path, switch the path
                    self.graph.switchPath(self.paths[peerAs].path, asPath, utcTime)
                    self.paths[peerAs].switchPath(asPath, utcTime)
                else: #the path is a duplicate announce, do nothing
                    self.numDupAnnounces += 1
                    return          
            else:
                self.activePaths += 1
                self.graph.startPath(asPath, utcTime)
                self.paths[peerAs].startPath(asPath, utcTime)
                
        elif type == 'W': # Remove the path
            self.numWithdrawals += 1
            if peerAs in self.paths: #if the path exists, stop the path and remove it from our set of paths
                if self.paths[peerAs].isActive():
                    self.graph.stopPath(self.paths[peerAs].path, utcTime)                    
                    self.paths[peerAs].stopPath(utcTime)
                    self.activePaths -= 1
                    
        else: #if we receive an update that's not an announce or withdrawal, raise an exception so we know
            raise    

#        self.graph.printState(utcTime)
        
        if not self.converged:
            self.testConvergence(utcTime)
        else:
            self.updateStats(utcTime)
        
    def testConvergence(self, utcTime):
    	if len(self.paths) == TOTAL_PEERS:
    	    print "REACHED CONVERGED STATE at update number %i" % (self.numAnnounces + self.numWithdrawals)
    	    self.converged = True
    	    self.convergenceTime = utcTime
    	    self.numAnnounces = 0
    	    self.numWithdrawals = 0
    	    self.numDupAnnounces = 0
    	    self.graph.resetStats(utcTime)
    	    self.stats["tot_nodes"] = StatTracker.StatTracker()
    	    for key in self.paths.keys():
        		self.paths[key].reset(utcTime)

    def loadExistingState(self):
        existingDates = self.dbHelper.getExistingDates()
        if not existingDates:
            return
        
        existingStartDate = existingDates[0]
        existingEndDate = existingDates[1]
        existingConvDate = existingDates[2]
        if (self.startDate < existingStartDate):
            self.dbHelper.deletePrefix()
            self.dbHelper.commit()
        elif (self.startDate > (existingEndDate + datetime.timedelta(seconds=1))):
            raise exceptions.ValueError("Start date after end date of existing data for prefix")
        elif (self.endDate <= existingEndDate):
            self.dbHelper.deletePrefix()
            self.dbHelper.commit()            
            #self.dataStartDate = None
        elif (self.endDate > existingEndDate): #there's data to load
            self.startDate = existingStartDate
            self.dataStartDate = existingEndDate + datetime.timedelta(seconds=1)
            
            counts = self.dbHelper.getUpdateCounts()
            self.numAnnounces = counts[0]
            self.numDupAnnounces = counts[1]
            self.numWithdrawals = counts[2]
            
            self.converged = (existingDates[3] == 'Y')
            if (self.converged):
                self.convergenceTime = int(time.mktime(existingConvDate.timetuple())) - time.altzone
            existingEndTime = int(time.mktime(existingEndDate.timetuple())) - time.altzone
            elapsedTime =  existingEndTime - (int(time.mktime(existingConvDate.timetuple())) - time.altzone)
            
            for stat in STAT_TYPES:
                statVals = self.dbHelper.getStat(stat)
                self.stats[stat].loadStat(statVals[0], statVals[1], statVals[2], statVals[3], statVals[4], existingEndTime, elapsedTime)
                
            for state in self.dbHelper.getPathStates():
                asPath = map(int, state[2].split(','))
                self.paths[int(state[0])] = Path.Path(asPath, int(state[0]), state[1], state[3], existingEndTime, state[4])
                if self.paths[state[0]].isActive():
                    self.activePaths += 1
                
            for state in self.dbHelper.getNodeStates():
                initState = State.State(state[3], existingEndTime, state[1], state[2])
                self.graph.loadNode(state[0], initState)
                
            for state in self.dbHelper.getChildStates():
                initState = State.State(state[4], existingEndTime, state[2], state[3])
                self.graph.loadNodeChild(state[0], state[1], initState)
                
            for state in self.dbHelper.getParentStates():
                initState = State.State(state[4], existingEndTime, state[2], state[3])
                self.graph.loadNodeParent(state[0], state[1], initState)
                
            self.graph.ensureValidState()
        else:
            raise exceptions.RuntimeError("Reached code in PrefixGraph.loadExistingState that should be unreachable")
    
    def updateStats(self, utcTime):
    	actNodes = self.graph.activeNodeCount()
    	edgeCount = self.graph.edgeCount()
    	totNodes = self.graph.nodeCount()
    	self.stats["paths"].updateStat(self.activePaths, utcTime)
    	self.stats["act_nodes"].updateStat(actNodes, utcTime)
    	self.stats["edges"].updateStat(edgeCount, utcTime)
    	self.stats["tot_nodes"].updateStat(totNodes, utcTime)

    def printStats(self, utcTime):
    	print "Stats at %s(%s)" % (time.gmtime(utcTime), utcTime)
    	print "Announces: %s\nDuplicates: %s\nWithdrawals: %s" % (self.numAnnounces, self.numDupAnnounces, self.numWithdrawals)
    	for k,v in self.stats.items():
    	    print k
    	    print "Mean: %s Max: %s Min: %s Std Dev: %s Last: %s" % (v.mean(), v.max, v.min, v.stdDev(), v.last)

    def writeStatsToDB(self, utcTime):
        if len(self.graph.nodes) == 0:
            return
        
    	if self.converged == False:
            self.convergenceTime = 0
            converged = 'N'
            timeSinceConv = utcTime - (int(time.mktime(self.startDate.timetuple())) - time.altzone)
        else:
            timeSinceConv = utcTime - self.convergenceTime
            converged = 'Y'
                
        totUpdates = self.numAnnounces + self.numWithdrawals
        nonDupAnn = self.numAnnounces - self.numDupAnnounces

        self.updateStats(utcTime)
        
        self.dbHelper.addPrefix(self.startDate, self.endDate, self.convergenceTime, converged, totUpdates, 
                                self.numAnnounces, self.numDupAnnounces, nonDupAnn, self.numWithdrawals)

        for stat, values in self.stats.items():
            self.dbHelper.addStat(stat, values.mean(), values.max, values.min, values.stdDev(), values.last)

        self.dbHelper.addNodeStates(self.graph.nodeStates(), utcTime, timeSinceConv)
        self.dbHelper.addNodeRanks(self.graph.nodeRanks(utcTime))
        self.dbHelper.addEdgeStates(self.graph.childStates(), "child", utcTime, timeSinceConv)
        self.dbHelper.addEdgeStates(self.graph.parentStates(), "parent", utcTime, timeSinceConv)
        peerStatesDict = {}
        peerPathDict = {}
        for path in self.paths.values():
            peerStatesDict[path.peerAs] = path.state
            peerPathDict[path.peerAs] = path.pathAsList()
        self.dbHelper.addPathStates(peerStatesDict, peerPathDict, utcTime, timeSinceConv)
        
        self.dbHelper.commit()